/**
 * 
 */
package uk.ac.manchester.tewpl.iceiface.server.service;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dozer.DozerBeanMapper;
import org.explorium.api.exception.KnowledgeBaseException;
import org.explorium.api.model.KBClass;
import org.explorium.api.model.KBCommunity;
import org.explorium.api.model.KBDatatypeProperty;
import org.explorium.api.model.KBDatatypePropertyValue;
import org.explorium.api.model.KBIndividual;
import org.explorium.api.model.KBInfo;
import org.explorium.api.model.KBObjectProperty;
import org.explorium.api.model.KBResource;
import org.explorium.api.model.KBRestriction;
import org.explorium.api.repository.kb.KBRepositoryProvider;
import org.explorium.api.repository.kb.KBRepositoryProviderManager;
import org.explorium.api.repository.kb.KnowledgeBase;
import org.explorium.core.bus.Bus;
import org.explorium.core.model.KBInfoImpl;
import org.explorium.core.repository.db.KBCommunityDBRepository;
import org.explorium.core.repository.db.KBInfoDBRepository;
import org.springframework.beans.factory.annotation.Autowired;

import uk.ac.manchester.tewpl.iceiface.client.ExploriumClientException;
import uk.ac.manchester.tewpl.iceiface.client.model.KBClassModel;
import uk.ac.manchester.tewpl.iceiface.client.model.KBCommunityModel;
import uk.ac.manchester.tewpl.iceiface.client.model.KBDatatypePropertyModel;
import uk.ac.manchester.tewpl.iceiface.client.model.KBDatatypePropertyValueModel;
import uk.ac.manchester.tewpl.iceiface.client.model.KBIndividualModel;
import uk.ac.manchester.tewpl.iceiface.client.model.KBInfoModel;
import uk.ac.manchester.tewpl.iceiface.client.model.KBObjectPropertyModel;
import uk.ac.manchester.tewpl.iceiface.client.model.KBObjectPropertyValueModel;
import uk.ac.manchester.tewpl.iceiface.client.model.KBResourceModel;
import uk.ac.manchester.tewpl.iceiface.client.model.KBRestrictionModel;
import uk.ac.manchester.tewpl.iceiface.client.model.KBWorkspaceModel;
import uk.ac.manchester.tewpl.iceiface.client.service.KBService;
import uk.ac.manchester.tewpl.iceiface.client.tree.Node;
import uk.ac.manchester.tewpl.iceiface.client.tree.Tree;
import uk.ac.manchester.tewpl.iceiface.client.util.LRUCache;
import uk.ac.manchester.tewpl.iceiface.server.util.DozerListMapper;

import com.extjs.gxt.ui.client.Style.SortDir;
import com.extjs.gxt.ui.client.data.BasePagingLoadResult;
import com.extjs.gxt.ui.client.data.PagingLoadConfig;
import com.extjs.gxt.ui.client.data.PagingLoadResult;
import com.hp.hpl.jena.sparql.util.Base64.OutputStream;

/**
 * @author roland
 * 
 */
public class KBServiceImpl implements KBService {
	private static final Log LOG = LogFactory.getLog(KBServiceImpl.class);

	@Autowired
	protected KBInfoDBRepository kbInfoRepository;
	protected KBRepositoryProvider provider;
	@Autowired
	KBCommunityDBRepository communityRepository;

	// For transaction isolation <UUID, KBInstance>
	private Map<String, KnowledgeBase> transactionalKBs = new HashMap<String, KnowledgeBase>();

	// Without transaction <URI, KBInstance>
	private LRUCache<String, KnowledgeBase> kbInstances = new LRUCache<String, KnowledgeBase>(
			100);

	@Autowired
	DozerBeanMapper mapper;

	@Autowired
	DozerListMapper listMapper;

	Bus bus;

	public void setBus(Bus bus) {
		this.bus = bus;

		// Set KnowledgeBase Repository Provider
		this.provider = bus.getExtension(KBRepositoryProviderManager.class)
				.getKBRepositoryProvider("owl");
		if (this.provider == null) {
			LOG.debug("Provider is NULL :-(");
		}
	}

	public void setKbInfoRepository(KBInfoDBRepository kbInfoRepository) {
		this.kbInfoRepository = kbInfoRepository;
	}

	public void setMapper(DozerBeanMapper mapper) {
		this.mapper = mapper;
	}

	public void setListMapper(DozerListMapper listMapper) {
		this.listMapper = listMapper;
	}

	public void setCommunityRepository(
			KBCommunityDBRepository communityRepository) {
		this.communityRepository = communityRepository;
	}

	public Collection<KBWorkspaceModel> findWorkspacesByCommunity(
			KBCommunityModel community) throws ExploriumClientException {
		KBCommunity c = communityRepository.findById(community.getId());
		return listMapper.map(c.getWorkspaces(), KBWorkspaceModel.class);
	}

	public KBInfoModel saveKBInfo(KBInfoModel kb)
			throws ExploriumClientException {
		KBInfo k = new KBInfoImpl();
		mapper.map(kb, k);
		LOG.debug("Mapped WORKSPACE Info ID = " + k.getId());

		k = kbInfoRepository.update(k);
		return (KBInfoModel) mapper.map(kb, KBInfoModel.class);
	}

	public KBInfoModel createKnowledgeBase(String uri, String name,
			String description)
			throws ExploriumClientException {
		KnowledgeBase kb;
		try {
			kb = provider.createKnowledgeBase(uri, name, description);

			//kbInstances.put(uri, kb);
			return (KBInfoModel) mapper.map(kb.getInfo(), KBInfoModel.class);
		} catch (KnowledgeBaseException e) {
			e.printStackTrace();
			throw new ExploriumClientException(e);
		}

	}
	
	public void deleteKnowledgeBaseByUri(String uri)
			throws ExploriumClientException {
		provider.removeKnowledgeBase(uri);
	}

	public void deleteClass(KBClassModel cls) throws ExploriumClientException {
		try {
			KnowledgeBase kb = getCachedKnowledgeBase(cls.getKB().getUri());

		} catch (KnowledgeBaseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			throw new ExploriumClientException(e);
		}

	}

	static long MAX_HIERARCHY_DEPTH = 50;

	public Tree<KBClassModel> findClassHierarchy(KBWorkspaceModel ws)
			throws ExploriumClientException {
		Tree<KBClassModel> tree = new Tree<KBClassModel>();
		Node<KBClassModel> root = new Node<KBClassModel>();
		try {
			KnowledgeBase kb = getCachedKnowledgeBase(ws.getKbInfo().getUri());
			boolean oldReasoning = kb.getReasoning();
			kb.startTransaction();
			kb.setReasoning(true);
			
			Collection<KBClass> classes = null;
			kb.setActiveWorkspace(ws.getUri());
			classes = kb.findClasses(true);
			if (classes != null) {
				LOG.debug("Returned " + classes.size() + " root classes");
				for (KBClass cls : classes) {
					doFindClassHierarchy(kb, ws, root, cls,
							new ArrayList<String>(), 0);
				}
			}
			kb.setReasoning(oldReasoning);
			kb.commitTransaction();
		} catch (KnowledgeBaseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			throw new ExploriumClientException(e);

		}
		tree.setRootElement(root);
		return tree;
	}
	
	public Collection<KBClassModel> findRootClasses(KBWorkspaceModel ws) throws ExploriumClientException{
		KnowledgeBase kb;
		try {
			kb = getCachedKnowledgeBase(ws.getKbInfo().getUri());
		} catch (KnowledgeBaseException e) {
			throw new ExploriumClientException(e);
		}
		kb.setActiveWorkspace(ws.getUri());
		return listMapper.map(kb.findClasses(true), KBClassModel.class);
	}

	private void doFindClassHierarchy(KnowledgeBase kb, KBWorkspaceModel ws,
			Node<KBClassModel> node, KBClass cls, List<String> occurs, int depth) {
		// System.out.println("Depth: " + depth + ", " + cls.getUri());
		
		//mapping excludes restrictions (complex Map)
		KBClassModel kbclsm = (KBClassModel) mapper.map(cls, KBClassModel.class);
		// map restrictions manually:
		Map<KBObjectProperty, Collection<KBRestriction>> srcRestrictions = cls.getRestrictions();
		Map<KBObjectPropertyModel, Collection<KBRestrictionModel>> destRestrictions = new HashMap<KBObjectPropertyModel, Collection<KBRestrictionModel>>();
		for (KBObjectProperty srcProp : srcRestrictions.keySet()){
			KBObjectPropertyModel destProp = (KBObjectPropertyModel) mapper.map(srcProp, KBObjectPropertyModel.class);
			Collection<KBRestriction> srcRestrs = srcRestrictions.get(srcProp);
			Collection<KBRestrictionModel> destRestrs = listMapper.map(srcRestrs, KBRestrictionModel.class);
			destRestrictions.put(destProp, destRestrs);
		}
		kbclsm.set(KBClassModel.RESTRICTIONS, destRestrictions);
		
		Node<KBClassModel> clsNode = new Node<KBClassModel>(kbclsm);
		node.addChild(clsNode);
		if ((depth >= MAX_HIERARCHY_DEPTH) || occurs.contains(cls.getUri()))
			return;

		Collection<KBClass> subClasses;
		try {
			subClasses = kb.findSubClasses(cls.getUri());
			if (subClasses != null) {
				for (KBClass sc : subClasses) {
					occurs.add(cls.getUri());
					doFindClassHierarchy(kb, ws, clsNode, sc, occurs, depth + 1);
					occurs.remove(cls.getUri());
				}
			}
		} catch (KnowledgeBaseException e) {
			e.printStackTrace(); // TODO Rethrow to client?
		}

	}

	public KBClassModel saveClass(KBClassModel cls) {
		// TODO Auto-generated method stub
		return cls;
	}

	public void deleteIndividual(String workspaceUri, KBIndividualModel individual)
			throws ExploriumClientException {
		try {
			KnowledgeBase kb = getCachedKnowledgeBase(individual.getKB()
					.getUri());
			if (kb != null) {
				try {
					kb.startTransaction();
					kb.setActiveWorkspace(workspaceUri);
					kb.removeIndividual(individual.getUri());
					kb.commitTransaction();
				} catch (Exception e) {
					kb.rollbackTransaction();
					throw new ExploriumClientException(e);
				}
				individualListCache.clear();
			}
		} catch (KnowledgeBaseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			throw new ExploriumClientException(e);
		}
	}

	public Collection<KBIndividualModel> findIndividuals(
			KBClassModel cls,
			KBWorkspaceModel ws)
			throws ExploriumClientException {
		try {
			LOG.debug("Loading individuals for " + cls.getUri() + " in WORKSPACE "
					+ cls.getKB().getName());
			KnowledgeBase kb = getCachedKnowledgeBase(cls.getKB().getUri());

			if (kb != null) {
				boolean oldReasoning = kb.getReasoning();
				
				//start transaction
				kb.startTransaction();
				kb.setActiveWorkspace(ws.getUri());
				kb.setReasoning(true);
				Collection<KBIndividual> list = kb
						.findIndividualsByClassUri(cls.getUri());
				kb.setReasoning(oldReasoning);;
				kb.commitTransaction();
				//end transaction - no rollback needed for read op
				
				return listMapper.map(list, KBIndividualModel.class);
			}
		} catch (KnowledgeBaseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			throw new ExploriumClientException(e);
		}

		return Collections.emptySet();
	}
	
	public Collection<KBIndividualModel> findIndividualsWithFilter (
			String filter,
			KBClassModel cls,
			KBWorkspaceModel ws)
			throws ExploriumClientException {
			
			try {
				LOG.debug("Loading individuals for " + cls.getUri() + " in WORKSPACE "
						+ cls.getKB().getName());
				KnowledgeBase kb = getCachedKnowledgeBase(cls.getKB().getUri());

				if (kb != null) {
					boolean oldReasoning = kb.getReasoning();
					
					//start transaction
					kb.startTransaction();
					kb.setActiveWorkspace(ws.getUri());
					kb.setReasoning(true);
					Collection<KBIndividual> list = kb
						.findIndividualsByClassUri(cls.getUri());
					kb.setReasoning(oldReasoning);;
					kb.commitTransaction();
					//end transaction - no rollback needed for read op
					
					// Filter list with regex, store +ves in filteredList
					Collection<KBIndividual> filteredList = new HashSet<KBIndividual>();
					for (KBIndividual indiv : list) {
						// Must test for both description and label being null
						String desc = (indiv.getDescription() != null ? indiv.getDescription().getValue() : null);
						String lbl = (indiv.getLabel() != null ? indiv.getLabel().getValue() : null);
						if ((desc != null && desc.matches("(?i).*" + filter + ".*"))
								|| (lbl != null && lbl.matches("(?i).*" + filter + ".*")))
							filteredList.add(indiv);
					}
					
					return listMapper.map(filteredList, KBIndividualModel.class); 
					
				}
			} catch (KnowledgeBaseException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				throw new ExploriumClientException(e);
			}

			return Collections.emptySet();
	}

	public KBIndividualModel saveIndividual(String workspaceUri, KBIndividualModel ind)
			throws ExploriumClientException {
		try {
			//LOG.debug("WORKSPACE URI = "+ind.getKB().getUri());
			KnowledgeBase kb = getCachedKnowledgeBase(ind.getKB().getUri());
			int c = 0;
			String[] classes = new String[ind.getClasses().size()];
			for (KBClassModel cls : ind.getClasses()){
				classes[c] = cls.getUri();
			}
			
			try {
				//start transaction
				kb.startTransaction();
				kb.setActiveWorkspace(workspaceUri);
				kb.createIndividual(classes, ind.getLabel(), ind.getDescription(), ind.getCreator(),
						ind.getSource(),ind.getComment());
				kb.flushCache();
				kb.commitTransaction();
				//end transaction
			} catch (Exception e) {
				kb.rollbackTransaction();
				throw new ExploriumClientException(e);
			}
			
			individualListCache.clear();
		} catch (KnowledgeBaseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			throw new ExploriumClientException(e);
		}
		return ind;
	}

	// May no longer be needed since back end caching is implemented
	LRUCache<String, Collection<KBIndividualModel>> individualListCache = new LRUCache<String, Collection<KBIndividualModel>>(
			50);

	public PagingLoadResult<KBIndividualModel> findIndividualsPaged(
			final PagingLoadConfig config, KBClassModel cls, KBWorkspaceModel ws)
			throws ExploriumClientException {
		LOG.debug("Received paging load request for individuals of class "
				+ cls.getUri());
		String cacheKey = ws.getUri() + cls.getUri();
		Collection<KBIndividualModel> inds = individualListCache.get(cacheKey);
		if (inds == null) {
			LOG.debug("Cache miss - fetching again...");
			inds = new ArrayList<KBIndividualModel>(findIndividuals(cls, ws));
			individualListCache.put(cacheKey, inds);
		} else {
			LOG.debug("Fetched from cache...");
		}

		LOG.debug("Creating list from collection...");
		List<KBIndividualModel> list = new ArrayList<KBIndividualModel>(inds);
		LOG.debug("Done. Sorting...");

		final String sortField = config.getSortInfo().getSortField();
		final SortDir asc = config.getSortInfo().getSortDir();
		//if (sortField != null) {
			Collections.sort(list, new Comparator<KBIndividualModel>() {
				public int compare(KBIndividualModel o1, KBIndividualModel o2) {
					return SortDir.DESC.equals(asc) ? o2.getLabel().compareTo(
							o1.getLabel()) : o1.getLabel().compareTo(
							o2.getLabel());
				}
			});
	//	}

		LOG.debug("Done. Getting required offset");
		ArrayList<KBIndividualModel> sublist = new ArrayList<KBIndividualModel>();
		int start = config.getOffset();
		int limit = list.size();
		if (config.getLimit() > 0) {
			limit = Math.min(start + config.getLimit(), limit);
		}
		for (int i = config.getOffset(); i < limit; i++) {
			sublist.add(list.get(i));
		}
		LOG.debug("Done, returing to client");
		return new BasePagingLoadResult<KBIndividualModel>(sublist, config
				.getOffset(), list.size());

	}

	public BasePagingLoadResult<KBInfoModel> findAllKnowledgeBases() {
		return new BasePagingLoadResult<KBInfoModel>(
				new ArrayList<KBInfoModel>(listMapper.map(kbInfoRepository
						.findAll(), KBInfoModel.class)));
	}

	public Collection<KBDatatypePropertyModel> findDatatypePropertiesForIndividual(
			String workspaceUri,
			KBIndividualModel individual) throws ExploriumClientException {
		LOG
				.debug("Begin find all data properties for "
						+ individual.getLabel());
		try {
			KnowledgeBase kb = getCachedKnowledgeBase(individual.getKB()
					.getUri());
			
			//start transaction
			kb.startTransaction();
			kb.setActiveWorkspace(workspaceUri);
			Collection<KBDatatypeProperty> properties = kb
				.findIndividualDatatypeProperties(individual.getUri());
			kb.commitTransaction();
			//end transaction - no rollback needed for read op
			
			LOG.debug("Returned " + properties.size()
					+ " elements from server...beginning mapping...");
			Collection<KBDatatypePropertyModel> mappedList = listMapper.map(
					properties, KBDatatypePropertyModel.class);
			LOG.debug("Mapping finished...returning...");
			return mappedList;

		} catch (KnowledgeBaseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			throw new ExploriumClientException(e);
		}
	}

	public Collection<KBDatatypePropertyValueModel> findAllDataPropertyValues(
			String workspaceUri,
			KBIndividualModel individual) throws ExploriumClientException {
		LOG.debug("Begin find all data property values for "
				+ individual.getLabel());
		try {
			KnowledgeBase kb = getCachedKnowledgeBase(individual.getKB()
					.getUri());

			//start transaction
			kb.startTransaction();
			kb.setActiveWorkspace(workspaceUri);
			Map<KBDatatypeProperty, Collection<KBDatatypePropertyValue>> values = kb
			.findIndividualDatatypePropertyValues(individual.getUri());
			kb.commitTransaction();
			//end transaction - no rollback needed for read op
			
			LOG.debug("Returned " + values.size()
					+ " elements from server...beginning mapping...");
			Set<KBDatatypePropertyValueModel> valueSet = new HashSet<KBDatatypePropertyValueModel>();
			for (KBDatatypeProperty p : values.keySet()) {
				Collection<KBDatatypePropertyValueModel> mappedList = listMapper
						.map(values.get(p), KBDatatypePropertyValueModel.class);
				for (KBDatatypePropertyValueModel v : mappedList) {
					v
							.set(KBDatatypePropertyValueModel.PROPERTY_URI, p
									.getUri());
				}
				valueSet.addAll(mappedList);
			}
			LOG.debug("Mapping finished...returning...");

			return valueSet;

		} catch (KnowledgeBaseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			throw new ExploriumClientException(e);
		}
	}

	
	public Collection<KBObjectPropertyValueModel> findAllObjectPropertyValues(
			String workspaceUri,
			KBIndividualModel individual) throws ExploriumClientException {
		LOG.debug("Begin find all object property values for "
				+ individual.getLabel());
		try {
			KnowledgeBase kb = getCachedKnowledgeBase(individual.getKB()
					.getUri());
			//start transaction
			kb.startTransaction();
			boolean reasoning = kb.getReasoning();
			kb.setReasoning(true);
			kb.setActiveWorkspace(workspaceUri);
			Map<KBObjectProperty, Collection<KBIndividual>> values = kb
				.findIndividualObjectPropertyValues(individual.getUri());
			kb.setReasoning(reasoning);
			kb.commitTransaction();
			//end transaction - no rollback needed for read op
			
			LOG.debug("Returned " + values.size()
					+ " elements from server...beginning mapping...");
			Set<KBObjectPropertyValueModel> valueSet = new HashSet<KBObjectPropertyValueModel>();
			for (KBObjectProperty p : values.keySet()) {
				for (KBIndividual v : values.get(p)) {
					KBIndividualModel iM = (KBIndividualModel) mapper.map(v,
							KBIndividualModel.class);
					KBObjectPropertyValueModel oM = new KBObjectPropertyValueModel(
							iM.getProperties());

					oM.set(KBObjectPropertyValueModel.PROPERTY_URI, p.getUri());
					oM.set(KBObjectPropertyValueModel.VALUE, iM);
					
					//start transaction
					kb.startTransaction();
					kb.setActiveWorkspace(workspaceUri);
					Collection<KBClassModel> classes = listMapper.map(kb
							.findClassesOfIndividual(individual.getUri()),
							KBClassModel.class);
					kb.commitTransaction();
					//end transaction - no rollback needed for read op
					
					oM.set(KBIndividualModel.CLASSES, classes);
					StringBuffer sb = new StringBuffer();
					for (KBClassModel cls : classes) {
						sb.append(cls.getLabel());
						sb.append(" ");
					}
					oM.set(KBIndividualModel.CLASSES + "_string", "{ "
							+ sb.toString() + "}");
					valueSet.add(oM);
				}
			}
			LOG.debug("Mapping finished...returning...");

			return valueSet;

		} catch (KnowledgeBaseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			throw new ExploriumClientException(e);
		}
	}
	
	public Collection<KBObjectPropertyModel> findObjectPropertiesForIndividual(
			String workspaceUri,
			KBIndividualModel individual) throws ExploriumClientException {
		LOG.debug("Begin find all object properties for "
				+ individual.getLabel());

		try {
			KnowledgeBase kb = getCachedKnowledgeBase(individual.getKB()
					.getUri());
			
			//start transaction
			kb.startTransaction();
			boolean reasoning = kb.getReasoning();
			kb.setReasoning(true);
			kb.setActiveWorkspace(workspaceUri);
			Map<KBObjectProperty, Collection<KBIndividual>> map = kb.findIndividualObjectPropertyValues(individual.getUri());
			kb.setReasoning(reasoning);
			kb.commitTransaction();
			//end transaction - no rollback needed for read op
			
			Collection<KBObjectProperty> properties = map.keySet();
			
			LOG.debug("Returned " + properties.size()
					+ " elements from server...beginning mapping...");
			Collection<KBObjectPropertyModel> mappedList = listMapper.map(
					properties, KBObjectPropertyModel.class);
			LOG.debug("Mapping finished...returning...");
			return mappedList;

		} catch (KnowledgeBaseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			throw new ExploriumClientException(e);
		}
	}
	
	public Collection<KBClassModel> findSuperClasses(KBClassModel cls, KBWorkspaceModel ws) throws ExploriumClientException{
		try {
			KnowledgeBase kb = getCachedKnowledgeBase(cls.getKB().getUri());
			kb.startTransaction();
			kb.setActiveWorkspace(ws.getUri());
			Collection<KBClass> superClasses = kb.findSuperClasses(cls.getUri());
			kb.commitTransaction();
			Collection<KBClassModel> superClassesModel = listMapper.map(
					superClasses, KBClassModel.class);
			return superClassesModel;
		} catch (KnowledgeBaseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			throw new ExploriumClientException(e);
		}
	}
	
	public Collection<KBClassModel> findSubClasses(KBClassModel cls, KBWorkspaceModel ws) throws ExploriumClientException{
		try {
			KnowledgeBase kb = getCachedKnowledgeBase(cls.getKB().getUri());
			kb.startTransaction();
			kb.setActiveWorkspace(ws.getUri());
			Collection<KBClass> subClasses = kb.findSubClasses(cls.getUri());
			kb.commitTransaction();
			Collection<KBClassModel> subClassesModel = listMapper.map(
					subClasses, KBClassModel.class);
			return subClassesModel;
		} catch (KnowledgeBaseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			throw new ExploriumClientException(e);
		}
	}

	synchronized KnowledgeBase getCachedKnowledgeBase(String uri)
			throws KnowledgeBaseException {
		KnowledgeBase kb = kbInstances.get(uri);
		if (kb == null) {
			LOG.debug("Getting first non-transactional instance of WORKSPACE for "
					+ uri);
			kb = this.provider.getKnowledgeBase(uri);
			kb.setReasoning(true);
			kbInstances.put(uri, kb);
		}
		return kb;
	}

	public void purgeCachedKnowledgeBase(KBInfoModel kb) {
		kbInstances.put(kb.getUri(), null);
	}

	public KBDatatypePropertyValueModel saveDatatypePropertyValue(
			String workspaceUri,
			KBDatatypePropertyValueModel kdpv, KBDatatypePropertyModel kdp, KBIndividualModel ind) throws ExploriumClientException {
		try {
			KnowledgeBase kb = getCachedKnowledgeBase(kdp.getKB().getUri());
			
			//start transaction
			kb.startTransaction();
			kb.setActiveWorkspace(workspaceUri);
			kb.addDatatypePropertyValue(ind.getUri(), kdp.getUri(), kdpv.getValue(), kdpv.getDatatype());
			kb.flushCache();
			kb.commitTransaction();
			//end transaction - no rollback needed for read op
			
		} catch (KnowledgeBaseException e) {
			throw new ExploriumClientException(e);
		}
		

		return null;
	}

	public KBObjectPropertyValueModel saveObjectPropertyValue(
			String workspaceUri,
			KBIndividualModel value, KBObjectPropertyModel property,
			KBIndividualModel ind) throws ExploriumClientException {
		try {
			KnowledgeBase kb = getCachedKnowledgeBase(property.getKB().getUri());
			
			try {
				//start transaction
				kb.startTransaction();
				kb.setActiveWorkspace(workspaceUri);
				kb.addObjectPropertyValue(ind.getUri(), property.getUri(), value.getUri());
				kb.flushCache();
				kb.commitTransaction();
				//end transaction
			} catch (Exception e) {
				kb.rollbackTransaction();
				throw new ExploriumClientException(e);
			}
			
		} catch (KnowledgeBaseException e) {
			throw new ExploriumClientException(e);
		}
		return null;
	}

	public Collection<KBDatatypePropertyModel> findDatatypePropertiesForClass(
			KBClassModel cls) throws ExploriumClientException {
		try {
			KnowledgeBase kb = getCachedKnowledgeBase(cls.getKB().getUri());
			Collection<KBDatatypeProperty> props = kb.findClassDatatypeProperties(cls.getUri());
			Collection<KBDatatypePropertyModel> model = listMapper.map(props, KBDatatypePropertyModel.class);
			return model;
		} catch (KnowledgeBaseException e) {
			throw new ExploriumClientException("Failed to find Datatype properties for Class "+cls.getLabel(), e);
		}
	}

	public Collection<KBObjectPropertyModel> findObjectPropertiesForClass(
			KBClassModel cls) throws ExploriumClientException {
		try {
			KnowledgeBase kb = getCachedKnowledgeBase(cls.getKB().getUri());
			Collection<KBObjectProperty> props = kb.findClassObjectProperties(cls.getUri());
			Collection<KBObjectPropertyModel> model = listMapper.map(props, KBObjectPropertyModel.class);
			return model;
		} catch (KnowledgeBaseException e) {
			throw new ExploriumClientException("Failed to find Object properties for Class "+cls.getLabel(), e);
			}
	}
	
	public Collection<KBClassModel> findClassesFromCSV(String kbURI, String ws, String csv) throws ExploriumClientException {
		try {
			String[] uris = csv.split(",",0);
			Collection<KBClass> clss = new ArrayList<KBClass>();
			KnowledgeBase kb = getCachedKnowledgeBase(kbURI);
			kb.startTransaction();
			kb.setActiveWorkspace(ws);
			for (int i=0; i<uris.length; i++){
				KBClass cls = kb.findClass(uris[i]);
				clss.add(cls);
			}
			kb.commitTransaction();
			return listMapper.map(clss, KBClassModel.class);
		} catch (KnowledgeBaseException e) {
			throw new ExploriumClientException("Failed to get Classes from CSV");
		}
	}
	
	public Collection<KBIndividualModel> findRelatedIndividualsOfClass(String workspaceUri, KBIndividualModel ind, KBClassModel cls) throws ExploriumClientException{
		try {
			KnowledgeBase kb = getCachedKnowledgeBase(ind.getKB().getUri());
			//start transaction
			kb.startTransaction();
			kb.setActiveWorkspace(workspaceUri);
			Collection<KBIndividual> results = kb.findRelatedIndividualsOfClass(ind.getUri(), cls.getUri());
			kb.commitTransaction();
			//end transaction - no rollback needed for read op
			return listMapper.map(results, KBIndividualModel.class);
		} catch (KnowledgeBaseException e) {
			throw new ExploriumClientException("Failed to get ");
		}
	}
	
	public KBClassModel findClass(String classUri, KBWorkspaceModel ws) throws ExploriumClientException {
		KnowledgeBase kb;
		try {
			kb = getCachedKnowledgeBase(ws.getKbInfo().getUri());
			kb.setActiveWorkspace(ws.getUri());
			return mapper.map(kb.findClass(classUri), KBClassModel.class);
		} catch (KnowledgeBaseException e) {
			throw new ExploriumClientException(e);
		}
	}
	
	public Collection<Map<String, KBIndividualModel>> executeQuery(String query, KBWorkspaceModel ws) throws ExploriumClientException {
		KnowledgeBase kb;
		try {
			kb = getCachedKnowledgeBase(ws.getKbInfo().getUri());
			kb.setActiveWorkspace(ws.getUri());
			
			Collection<Map<String, KBIndividual>> results = null;
			Collection<Map<String, KBIndividualModel>> output = new ArrayList<Map<String, KBIndividualModel>>();
			
			try {
				Collection<Map<String, KBResource>> resourceMaps = kb.executeQuery(query);
				for (Map<String, KBResource> map : resourceMaps) {
					for (String key : map.keySet()) {
						Map<String, KBIndividualModel> newMap = new HashMap<String, KBIndividualModel>();
						KBResource res = map.get(key);
						newMap.put(key, mapper.map((KBIndividual) res, KBIndividualModel.class));
						output.add(newMap);
					}
				}
			} catch (Exception e) {
				throw new ExploriumClientException("SPARQL Error: " + e.getMessage());
			}
			
			return output;
			
			
			/*if (results != null) {
				for (Map<String, KBIndividual> map : results) {
					HashMap<String, KBIndividualModel> mappings = new HashMap<String, KBIndividualModel>();
					for (String key : map.keySet()) {
						mappings.put(key, mapper.map(map.get(key), KBIndividualModel.class));
					}
					output.add(mappings);
				}
				return output;
			} else {
				return null;
			}*/

		
		} catch (Exception e) {
			throw new ExploriumClientException(e.getMessage());
		}
	}
	
	public Map<KBObjectPropertyModel, Collection<KBIndividualModel>> findIndividualObjectPropertyValues(String individualUri, KBWorkspaceModel ws) throws ExploriumClientException {
		KnowledgeBase kb;
		try {
			kb = getCachedKnowledgeBase(ws.getKbInfo().getUri());
			kb.setActiveWorkspace(ws.getUri());
			
			Map<KBObjectProperty, Collection<KBIndividual>> results = kb.findIndividualObjectPropertyValues(individualUri);
			
			Map<KBObjectPropertyModel, Collection<KBIndividualModel>> output = new HashMap<KBObjectPropertyModel, Collection<KBIndividualModel>>(); 
			
			if (results != null) {
				for (KBObjectProperty key : results.keySet()) {
					output.put(mapper.map(key, KBObjectPropertyModel.class), listMapper.map(results.get(key), KBIndividualModel.class));
				}
				return output;
			} else
				return null;
			
			/*if (result.keySet().size() > 0) {
				KBObjectProperty key = result.keySet().iterator().next();
				KBObjectPropertyModel newKey = mapper.map(key, KBObjectPropertyModel.class);
				
				Map<KBObjectPropertyModel, Collection<KBIndividualModel>> map = new HashMap<KBObjectPropertyModel, Collection<KBIndividualModel>>();
				map.put(newKey, listMapper.map(result.get(key), KBIndividualModel.class));
				return map;
			} else
				return null;*/
			
		} catch (Exception e) {
			throw new ExploriumClientException(e.getMessage());
		}
	}
	
	public KBIndividualModel findIndividual(String individualUri, KBWorkspaceModel ws) throws ExploriumClientException {
		KnowledgeBase kb;
		try {
			kb = getCachedKnowledgeBase(ws.getKbInfo().getUri());
			kb.setActiveWorkspace(ws.getUri());

			return mapper.map(kb.findIndividual(individualUri), KBIndividualModel.class);
			
		} catch (Exception e) {
			throw new ExploriumClientException(e.getMessage());
		}
	}
	
	public int getSparqlCount(String sparql, KBWorkspaceModel ws) {
		KnowledgeBase kb;
		try {
			kb = getCachedKnowledgeBase(ws.getKbInfo().getUri());
			kb.setActiveWorkspace(ws.getUri());
			
			

		} catch (Exception e) {
			
		}
		return 0;
	}
	
}
